// This is a 'go generate'-oriented program for producing the "Variables"
// method on every Expression implementation found within this package.
// All expressions share the same implementation for this method, which
// just wraps the package-level function "Variables" and uses an AST walk
// to do its work.

// +build ignore

package main

import (
    "fmt"
    "go/ast"
    "go/parser"
    "go/token"
    "os"
    "sort"
)

func main() {
    fs := token.NewFileSet()
    pkgs, err := parser.ParseDir(fs, ".", nil, 0)
    if err != nil {
        fmt.Fprintf(os.Stderr, "error while parsing: %s\n", err)
        os.Exit(1)
    }
    pkg := pkgs["hclsyntax"]

    // Walk all the files and collect the receivers of any "Value" methods
    // that look like they are trying to implement Expression.
    var recvs []string
    for _, f := range pkg.Files {
        for _, decl := range f.Decls {
            fd, ok := decl.(*ast.FuncDecl)
            if !ok {
                continue
            }
            if fd.Name.Name != "Value" {
                continue
            }
            results := fd.Type.Results.List
            if len(results) != 2 {
                continue
            }
            valResult := fd.Type.Results.List[0].Type.(*ast.SelectorExpr).X.(*ast.Ident)
            diagsResult := fd.Type.Results.List[1].Type.(*ast.SelectorExpr).X.(*ast.Ident)

            if valResult.Name != "cty" && diagsResult.Name != "hcl" {
                continue
            }

            // If we have a method called Value and it returns something in
            // "cty" followed by something in "hcl" then that's specific enough
            // for now, even though this is not 100% exact as a correct
            // implementation of Value.

            recvTy := fd.Recv.List[0].Type

            switch rtt := recvTy.(type) {
            case *ast.StarExpr:
                name := rtt.X.(*ast.Ident).Name
                recvs = append(recvs, fmt.Sprintf("*%s", name))
            default:
                fmt.Fprintf(os.Stderr, "don't know what to do with a %T receiver\n", recvTy)
            }

        }
    }

    sort.Strings(recvs)

    of, err := os.OpenFile("expression_vars.go", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm)
    if err != nil {
        fmt.Fprintf(os.Stderr, "failed to open output file: %s\n", err)
        os.Exit(1)
    }

    fmt.Fprint(of, outputPreamble)
    for _, recv := range recvs {
        fmt.Fprintf(of, outputMethodFmt, recv)
    }
    fmt.Fprint(of, "\n")

}

const outputPreamble = `package hclsyntax

// Generated by expression_vars_get.go. DO NOT EDIT.
// Run 'go generate' on this package to update the set of functions here.

import (
	"Havoc/pkg/profile/yaotl"
)`

const outputMethodFmt = `

func (e %s) Variables() []hcl.Traversal {
	return Variables(e)
}`
